Integrate 3rd Party Javascript Libraries In ZK Using Clientside Controller

From Documentation
DocumentationSmall Talks2013FebruaryIntegrate 3rd Party Javascript Libraries In ZK Using Clientside Controller
Integrate 3rd Party Javascript Libraries In ZK Using Clientside Controller

Author
Ashish Dasnurkar, Engineer, Potix Corporation
Date
February 07, 2013
Version
ZK 6.0 and later


Overview

In earlier article I introduced how you could integrate a 3rd party JS library with little knowledge of ZK's client side programming techniques. In this part I will demonstrate few advanced concepts of ZK client side programming allowing you to define a client side controller to encapsulate 3rd party JS library integration to make it more readable, reusable and easy to maintain.

Please note that for brevity I will only show code snippets to describe important parts of the code. For complete source code see Downloads section

Advanced concepts orientation

I will first describe few key concepts and ideas on how I will use them in the following sample.

Widget

Suppose you define a component for eg. window as shown below in your zul file

<window title="ZK Rocks" border="normal">
</window>

Now whenever ZK processes this zul file ZK creates two parts;a component represented as a Java object of org.zkoss.zul.Window class that resides on the server side and a widget, as represented by a Javascript object that resides in browser i.e. client side. Now here is the key, just like one can customize a component by extending its Java class on server side, a widget on client side can also be customized by extending it using ZK's Javascript APIs. For eg. you can extend Window widget in Javascript using zk.$extends(Class, Map, Map) as shown below

zul.wgt.MyWindowWidget = zk.$extends(zul.wnd.Window, {
...
}

Use attribute

So you have customized or created a new Widget but how do you instruct ZK to use this custom widget instead of default one? It can be done by specifying use client attribute on component tag and specifying fully qualified Javascript class of your widget.

	<window xmlns:w="client" w:use="zul.wgt.MyWindowWidget">
	</window>

Lifecycle

Once the widget is created on client side it follows a simple lifecycle. For more specific details, refer to this section from ZK's component development essentials. Briefly speaking, first the DOM tree representing the visual part of widget is created and attached to html document, followed by a call to bind_ method. bind_ is where typically any widget events are registered and also widget specific initialization can be done. Similarly there is unbind_ method which is invoked when widget is detached from DOM tree to un-register any events and/or release data specific to widget to avoid memory leak.

Communicating with the widget on client side

Widget is just a Javascript object so you can call any method on it by first getting a reference to it using zk.Widget.$() API and calling specific method directly

Communicating with the widget from server side

Unlike communicating with serverside via events as introduced earlier, when you want to communicate with widget from serverside you can do it by sending a command to ZK's client engine. ZK provides a utility API Clients.response(AuResponse) for this and for example can be used as shown below

	Clients.response(new AuInvoke(mycomp, "doRefresh", new string("new data")));

Here AuInvoke is a derived implementation of AuResponse that allows developer to identify widget, its method to be invoked and additionally any data that needs to be passed to it.

Now let's re-implement the same example using above concepts

SpcaeTree layout demo

Here is the demo of example

Here we use two SpaceTree layout from Infovis Javascript Toolkit. You can click on any of these two SpaceTree layouts to perform add node,refresh or change orientation.

Implementation details

I will follow the same common tasks involved in integrating 3rd party JS library with ZK as introduced earlier and show you how we can make use of the concepts described above.

Include library source

You can include Javascript files using script xml processing instruction on your ZUML page. For this sample I downloaded Infovis toolkit Javascript library file and included it as shown below

	<?script src="jit.js" ?>

Also for better organization of source code I have created a separate JS file for my custom code for this sample and included it similarly

	<?script src="sampleAdv.js" ?>

Setup basic components

To show the SpaceTree layout visualization we need a div component. I have also added a textbox and a button for adding a new node feature.

<zk>
	<window id="mainWin" xmlns:w="client">
		<vlayout>
			<div sclass="container">
				<div id="infovis" w:use="zul.wgt.InfovisDiv" width="600px" height="300px"/>
			</div>
			<groupbox width="600px">
				<caption label="Add/Refresh Nodes"/>
			<hlayout width="590px">
				<textbox hflex="1" id="nodeTxt" />
				<button id="add" label="Add" />
				<button id="refresh" label="Refresh" />
			</hlayout>
			</groupbox>
		</vlayout>
	</window>
</zk>

In addition to this I will also create a custom widget for div that will host Infovis SpaceTree layout. For this we can use the zk.$extends(Class, Map, Map) API introduced earlier.

zk.load("zul.wgt", function () {
	zul.wgt.InfovisDiv = zk.$extends(zul.wgt.Div, {
	});
});

Prepare data for library

I prepare the data same as introduced in the first part. There is no change in the implemntation for this part. Please refer to same section from earlier smalltalk

Initilizate/Configure library

For initializing Infovis Javascript toolkit and configuring SpaceTree layout I will make use of bind_ life-cycle method described above.

zk.load("zul.wgt", function () {
	zul.wgt.InfovisDiv = zk.$extends(zul.wgt.Div, {
		bind_: function(desktop, skipper, after){
			this.$supers('bind_', arguments);
			// initialize and/or config 3rd party library here
		},
		_init: function(treeData){
			var removing = false;
		    //init Spacetree
		    //Create a new ST instance
		    var wrapper = '' + this.$n().id; //this.$n() return the root html element of this widget
		    this._st = new $jit.ST({
		        'injectInto': wrapper,
			    // rest of the init function
			}
			this._refresh(treeData); // show the SpaceTree layout
		},
		_refresh: function(treeData) {
		    this._st.loadJSON(treeData);
		    //compute node positions and layout
		    this._st.compute();
		    //optional: make a translation of the tree
		    this._st.geom.translate(new $jit.Complex(-200, 0), "current");
		    //Emulate a click on the root node. 
		    this._st.onClick(this._st.root);
		    //end
		},	
		// rest of the widget methods
	});
});

I maintain a widget variable named _st to hold reference to SpaceTree layout instance. Also note that I have separated this process into several methods. bind_ method configures SpaceTree layout, _init creates SpaceTree layout instance as it should be done only when SpaceTree layout is required to be shown and finally _refresh method to load data into SapceTree layout as it might be required frequently and can be called several times later.

Note that whereas bind_ method of our widget will be invoked by ZK during the lifecycle of widget, we need to invoke _init ourselves. Calling _init,_refresh or any other widget methods that in turn call Javascript library functions is described in the next section

Calling library functions

Typically library function calls will be done via widget methods. For eg. I create SpaceTree layout instance in _init method by calling $jit.ST API of Infovis Javascript toolkit. Now we can call this _init method in two ways i.e from client side or from server side using the concepts described above.

Call widget method on client side

Get the instance of Widget using zk.Widget.$() API and call method as a normal Javascript function call on object.

var myST = zk.Widget.$("$infovis");
myST._init(treeData); // treeData here is the JSON representation of the SpaceTree

Call widget method from server side

Invoke Clients.response(AuResponse) method by identifying component/widget, widget method and pass optional data

Clients.response(new AuInvoke(infovis, "_init", spaceTreeNode)); //spaceTreeNode is JSONAware object representing SpaceTreeNode instance

Why should I use this approach?

What are the pros and cons of choosing a client side controller approach described here over the simpler one introduced in earlier smalltalk?

Pros

  1. No gobal state created thereby avoiding the evils of global variables to maintain state related to 3rd party Javascript library and avoid any variable/function conflict.
  2. Object oriented style of programming
  3. Can create multiple widgets with using same client side controller aka Widget class.

Cons

  1. Requires better understanding of ZK Widget lifecycle & Javascript APIs (However all of which is required is covered here)

Summary

In this article I introduced you how you can integrate with 3rd party Javascript libraries using a custom client side controller. In the course I introduced few key advanced concepts of ZK's client-side programming and advantages/disadvantages.

Downloads

You can get complete source for the example described in this smalltalk from its github repository Either do

git clone https://github.com/kachhalimbu/jslibinteg

or download zip archive from downloads section and import project into Eclipse as "Existing Maven Project"


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.